//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using System.Text;
using ER = Microsoft.Research.DataLayer;

namespace Microsoft.Research.DataLayer
{
    /// <summary>
    /// Containing the base Allow/Deny Permission functionality
    /// </summary>
    /// <typeparam name="P"></typeparam>
    public class AllowDenyPermissionBase<P> 
        where P : ER.IPrivilege
    {
        /// <summary>
        /// A securable object instance.
        /// </summary>
        private ISecurableObject obj;        
        /// <summary>
        /// The Allow privilege object.
        /// </summary>
        public P Allow;
        /// <summary>
        /// The Deny privilege object.
        /// </summary>
        public P Deny;
        /// <summary>
        /// The connection object.
        /// </summary>
        public Connection conn;

         /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="owner">Object that owns the collection</param>
        /// <param name="c">Valid connection to the storage backend</param>
        protected AllowDenyPermissionBase(ISecurableObject obj, Connection conn)
        {
            this.obj = obj;
            this.conn = conn;  
            
        }
        
        #region IPermission Members

        /// <summary>
        /// Performs the Allow/Deny Check for the specified principal.
        /// </summary>
        /// <param name="principal">The principal object.</param>
        /// <returns>value indicating whether the principal has the required permission on the object.</returns>
        public bool Check(ISecurityPrincipal principal)
        {
            // Get the system policy object.
            SystemPolicy policy = SystemPolicy.Get(this.conn);             
            
            // Object owner has all access
            if (obj.Owner.Equals(principal))
               return true;
            
            // Admin has all access. Treating members of services group as administrators for now.
            if (policy.Builtin.Administrators.Contains(principal) || policy.Builtin.Services.Contains(principal))
                return true;

            //Check if he belongs to the administrator group.
            if (principal is User)
            {
                User user = principal as User;
                user.Groups.Refresh();
                foreach (Group group in user.Groups)
                {                    
                    if (policy.Builtin.Administrators.Contains(group) || policy.Builtin.Services.Contains(group))
                    {
                        return true;
                    }
                }
            }


            // If the deny bit is set for "Everyone" return false. Note:Admin and owner still has full access.
            if (Deny.Check(policy.Everyone))
            {
                return false;
            }       
          
            // #1) Look for deny first in all security relations
            //    #1.1) Look for deny in users
            //    #1.2) Look for deny in groups
            if (Deny.Check(principal))
            {
                return false;                
            }
            // If its a user then look for deny in his groups.
            if (principal is User)
            {
                User user = principal as User;
                foreach (Group group in user.Groups)
                {                    
                    if (Deny.Check(group))
                    {
                        return false;
                    }
                }
            }

            //If the allow bit is set for "Everyone" return true.
            if (Allow.Check(policy.Everyone))
            {
                return true;
            }

            // #2) Look for allows in all security relations
            //    #2.1) Look for allow in users
            //    #2.2) Look for allow in groups
            if (Allow.Check(principal))
            {
                return true;
            }
            // If its a user then look for allow in his groups.
            if (principal is User)
            {
                User user = principal as User;
                foreach (Group group in user.Groups)
                {
                    if (obj.Owner.Equals(group))
                    {
                        return true;
                    }
                    else if (Allow.Check(group))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Ensures the specified principal has the required permission.
        /// </summary>
        /// <param name="principal">The principal object.</param>
        public void Ensure(ISecurityPrincipal principal)
        {
            if (!Check(principal))
            {
                throw new System.Security.SecurityException("Security principal does not hold permission");
            }
        }

        #endregion

        /// <summary>
        /// Gets the privileges for the permission.
        /// </summary>
        /// <value>The privilege objects array.</value>
        public IPrivilege[] Privileges
        {
            get { return new IPrivilege[] { Allow, Deny }; }
        }

    }
}
